package cate.game.pvp.eliterace.simulator;

import cate.game.framework.advanced.AdvancedByteAttributes;
import cate.game.framework.advanced.AdvancedLongAttributes;
import cate.game.pvp.eliterace.data.*;
import cate.game.pvp.eliterace.handler.*;
import cate.game.pvp.eliterace.timer.EliteRaceTimerTask.Settings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static cate.game.event.eliterace.EliteRaceTimerEvent.EventType.*;
import static cate.game.pvp.eliterace.data.EliteRaceKV.OP_SKIP_SEASON;

public class BothEliteRaceGroupSimulator extends EliteRaceSimulator {


    public BothEliteRaceGroupSimulator(Supplier<EliteRaceKV> kvSupplier,
                                       Supplier<EliteRaceRecordOperation> recordOperationSupplier,
                                       Supplier<EliteRacePsOperation> psOperationSupplier,
                                       Supplier<EliteRaceFightOperation> fightOperationSupplier,
                                       Supplier<EliteRaceRankRwdOperation> rankRwdOperationSupplier,
                                       Supplier<EliteRaceBetOperation> betOperationSupplier,
                                       Supplier<EliteRaceVideoOperation> videoOperationSupplier) {
        super(
                kvSupplier,
                recordOperationSupplier,
                psOperationSupplier,
                fightOperationSupplier,
                rankRwdOperationSupplier,
                betOperationSupplier,
                videoOperationSupplier
        );
    }

    @Override
    public boolean isMatch(Settings settings) {
        return settings.getEventType() == TRIAL_GROUP
                || settings.getEventType() == KO_GROUP
                || settings.getEventType() == FINAL_GROUP;
    }

    @Override
    public boolean runWith(Settings settings) {
        AdvancedByteAttributes opAttributes = getOpAttributes();
        AdvancedLongAttributes attributes = getAttributes();
        if (opAttributes.hasAttribute(OP_SKIP_SEASON)) {
            getKV().setSpec(settings.getSpec());
            attributes.setAttribute(settings.getEventType().getFlag());
            return true;
        }
        if (attributes.hasAttribute(settings.getEventType().getFlag())) {
            return true;
        }

        switch (settings.getEventType()) {
            case TRIAL_GROUP:
                runTrialGroupWith(settings);
                break;
            case KO_GROUP:
                runKoGroupWith(settings);
                break;
            case FINAL_GROUP:
                runFinalGroupWith(settings);
                break;
            default:
                break;
        }

        EliteRaceKV kv = getKV();
        kv.setSpec(settings.getSpec());

        attributes.setAttribute(settings.getEventType().getFlag());
        return true;
    }

    private void runFinalGroupWith(Settings settings) {
        EliteRacePsOperation psOperation = getPsOperation();
        List<EliteRacePs> sortedPs = psOperation.getSortedPs(
                Comparator.comparing(EliteRacePs::getOpponent, Comparator.comparing(EliteRaceOpponent::getWinCount, Comparator.reverseOrder()))
                        .thenComparing(EliteRacePs::getOpponent, Comparator.comparing(EliteRaceOpponent::getPoints, Comparator.reverseOrder())));
        List<String> finalPs = sortedPs.stream()
                .limit(4)
                .map(EliteRacePs::getUid)
                .collect(Collectors.toList());

        EliteRaceRecordOperation recordOperation = getRecordOperation();

        int size = finalPs.size();
        for (int i = 0; i < Math.min(2, size); i++) {
            String uidA = finalPs.get(i);
            String uidB = (i + 2) <= (size - 1) ? finalPs.get(i + 2) : null;
            if (StringUtils.hasLength(uidA) || StringUtils.hasLength(uidB)) {
                EliteRaceSpec spec = EliteRaceSpec.ofFinal(
                        settings.getSpec().getSeason(),
                        (byte) 1);
                EliteRaceRecord record = EliteRaceRecord.of(
                        spec,
                        StringUtils.hasLength(uidA) ? uidA : null,
                        StringUtils.hasLength(uidB) ? uidB : null);
                record.setIndex((byte) i);
                recordOperation.addRecord(record);
            }
        }
    }

    private void runKoGroupWith(Settings settings) {
        EliteRacePsOperation psOperation = getPsOperation();
        List<EliteRacePs> sortedPs = psOperation.getSortedPs(
                Comparator.comparing(EliteRacePs::getOpponent, Comparator.comparing(EliteRaceOpponent::getWinCount, Comparator.reverseOrder()))
                        .thenComparing(EliteRacePs::getOpponent, Comparator.comparing(EliteRaceOpponent::getPoints, Comparator.reverseOrder())));
        List<EliteRacePs> psList = sortedPs.stream()
                .limit(32)
                .collect(Collectors.toList());

        Map<Integer, List<EliteRacePs>> groupList = Maps.newHashMap();

        int c = 0;
        for (EliteRacePs ps : psList) {
            c = c % 4;
            groupList.computeIfAbsent(c, (s) -> Lists.newArrayList()).add(ps);
            c++;
        }

        EliteRaceRecordOperation recordOperation = getRecordOperation();

        for (Map.Entry<Integer, List<EliteRacePs>> entry : groupList.entrySet()) {
            int group = entry.getKey();
            List<EliteRacePs> gps = entry.getValue();
            gps.sort(Comparator.comparing(EliteRacePs::getOpponent, Comparator.comparing(EliteRaceOpponent::getWinCount, Comparator.reverseOrder()))
                    .thenComparing(EliteRacePs::getOpponent, Comparator.comparing(EliteRaceOpponent::getPoints, Comparator.reverseOrder())));
            int size = gps.size();

            for (int i = 0; i < Math.min(4, size); i++) {
                String uidA = gps.get(i).getUid();
                String uidB = (i + 4) <= (size - 1) ? gps.get(i + 4).getUid() : null;
                if (StringUtils.hasLength(uidA) || StringUtils.hasLength(uidB)) {
                    EliteRaceSpec spec = EliteRaceSpec.ofKo(
                            settings.getSpec().getSeason(),
                            (byte) group,
                            (byte) 1);
                    EliteRaceRecord record = EliteRaceRecord.of(
                            spec,
                            StringUtils.hasLength(uidA) ? uidA : null,
                            StringUtils.hasLength(uidB) ? uidB : null
                    );
                    record.setIndex((byte) i);
                    recordOperation.addRecord(record);
                }
            }
        }
    }

    private void runTrialGroupWith(Settings settings) {
        EliteRacePsOperation psOperation = getPsOperation();
        List<EliteRacePs> sortedPs = psOperation.getSortedPs(
                Comparator.comparing(EliteRacePs::getOpponent, Comparator.comparing(opp -> -opp.getSnapshot().base.power)));

        int size = sortedPs.size();

        EliteRaceRecordOperation recordOperation = getRecordOperation();
        if (size == 1) {
            for (int i = 0; i < 6; i++) {
                EliteRacePs psA = sortedPs.get(0);
                EliteRaceSpec spec = EliteRaceSpec.ofTrial(settings.getSpec().getSeason(), (byte) (i + 1));
                EliteRaceRecord record = EliteRaceRecord.of(spec, psA.getUid(), null);
                recordOperation.addRecord(record);
            }
        } else if (size <= 6) {
            for (EliteRacePs epsA : sortedPs) {
                List<EliteRacePs> oppList = sortedPs.stream()
                        .filter(eps -> eps != epsA)
                        .collect(Collectors.toList());
                for (int j = 0; j < 6; j++) {
                    EliteRacePs epsB = oppList.get(Math.min(oppList.size() - 1, j));
                    EliteRaceSpec spec = EliteRaceSpec.ofTrial(settings.getSpec().getSeason(), (byte) (j + 1));
                    EliteRaceRecord record = EliteRaceRecord.of(spec, epsA.getUid(), epsB.getUid());
                    recordOperation.addRecord(record);
                }
            }
        } else {
            int c = size % 6;
            int d = size / 6;
            Random random = new Random();
            for (EliteRacePs epsA : sortedPs) {
                List<EliteRacePs> oppList = sortedPs.stream()
                        .filter(eps -> eps != epsA)
                        .collect(Collectors.toList());
                int c1 = c;
                for (int i = 0; i < 6; i++) {
                    int s = (c1 > 0) ? d + 1 : d;
                    if (c1 > 0) {
                        c1--;
                    }
                    int r = random.nextInt(s) + i;
                    EliteRacePs epsB = oppList.get(r);
                    EliteRaceSpec spec = EliteRaceSpec.ofTrial(settings.getSpec().getSeason(), (byte) (i + 1));
                    EliteRaceRecord record = EliteRaceRecord.of(spec, epsA.getUid(), epsB.getUid());
                    recordOperation.addRecord(record);
                }
            }
        }
    }
}
